#include "widget.h"
#include "ui_widget.h"


Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    connect(ui->btn,&QPushButton::clicked,this,[=](){
        const char* lpszTaskName = "Run calc";   //任务名
        const char* lpszProgramPath = "c:\\windows\\system32\\cmd.exe";  //要执行的程序路径
        const char* lpszParameters = "";     //程序参数
        const char* lpszAuthor = "CMD task";   //任务作者描述

        Taskinit();
        BOOL bRet=CreateTask(lpszTaskName, lpszProgramPath, lpszParameters, lpszAuthor);
        if (!bRet) {
            qDebug() << "Create Task Failed";
        }
        UnInit();
        qDebug() << "执行完成";
    });
}

Widget::~Widget()
{
    delete ui;
}

void Widget::Taskinit()
{
    //1.CoInitialize初始化COM组件
    HRESULT hr = CoInitialize(NULL);
    if (FAILED(hr)) {
        MessageBox(NULL, L"初始化COM组件失败", L"Failed", MB_OK);
    }

    //2.CoCreateInstance创建任务服务对象
    hr = CoCreateInstance(CLSID_TaskScheduler,  //与将用于创建对象的数据和代码关联的 CLSID。
                          NULL,                 //如果 为 NULL，则指示对象不是作为聚合的一部分创建的。 如果不是 NULL，则指向聚合对象的 IUnknown 接口的指针 (控制 IUnknown) 。
                          CLSCTX_INPROC_SERVER, //管理新创建对象的代码将在其中运行的上下文。 这些值取自枚举 CLSCTX。
                          IID_ITaskService,     //对要用于与对象通信的接口标识符的引用。
                          (LPVOID*)&m_lpITS);   //接收 riid 中请求的接口指针的指针变量的地址。 成功返回后，*ppv 包含请求的接口指针。 失败时，*ppv 包含 NULL。
    if (FAILED(hr)) {
        MessageBox(NULL, L"创建任务服务失败", L"Failed", MB_OK);
    }
    //3.连接到任务服务
    hr = m_lpITS->Connect(_variant_t(), //要连接到的计算机的名称。 如果 serverName 参数为空，则此方法将在本地计算机上执行。
                          _variant_t(), //连接到计算机期间使用的用户名。 如果未指定 用户 ，则使用当前令牌。
                          _variant_t(), //在 user 参数中指定的 用户的 域。
                          _variant_t());    //用于连接到计算机的密码。 如果未指定用户名和密码，则使用当前令牌。
    if (FAILED(hr)) {
        MessageBox(NULL, L"连接服务失败", L"Failed", MB_OK);
    }
    //4.从ITaskService对象中获取根任务Root Task Folder的指针对象ITaskFolder，这个指针指向新注册的任务
    hr = m_lpITS->GetFolder(_bstr_t("\\"),  //要检索的文件夹的路径。 不要在路径中最后一个文件夹名称后面使用反斜杠。 使用反斜杠 (\) 指定根任务文件夹。 根任务文件夹下的任务文件夹路径示例为 \MyTaskFolder。 “.”字符不能用于指定当前任务文件夹和“..”。 字符不能用于指定路径中的父任务文件夹。
                            &m_lpRootFolder);   //请求的文件夹的 ITaskFolder 接口。
    if (FAILED(hr)) {
        MessageBox(NULL, L"获取指针失败", L"Failed", MB_OK);

    }
}

BOOL Widget::CreateTask(const char *lpszTaskName, const char *lpszProgramPath, const char *lpszParameters, const char *lpszAuthor)
{
    // 创建任务定义对象来创建任务
    ITaskDefinition* pTaskDefinition = NULL;    //定义任务的所有组件，例如任务设置、触发器、操作和注册信息。
    /*
     * [in] flags
     *  此参数保留供将来使用，必须设置为 0。
     * [out] ppDefinition
     * 指定创建新任务所需的所有信息的任务定义。
     * 传入对 NULLITaskDefinition 接口指针的引用。 引用非 NULL 指针可能会导致内存泄漏，因为指针将被覆盖。
     * 使用返回的 ITaskDefinition 指针后，必须释放该指针。
    */
    HRESULT hr = m_lpITS->NewTask(0, &pTaskDefinition);
    if (FAILED(hr))
    {
        qDebug() << 1;
        return FALSE;
    }

    /* 设置注册信息 */
    IRegistrationInfo* pRegInfo = NULL; //提供可用于描述任务的管理信息。 此信息包括任务的说明、任务的作者、任务的注册日期以及任务的安全描述符等详细信息。
    CComVariant variantAuthor(NULL);    //此类包装 VARIANT 类型，提供一个成员来指示存储的数据的类型。
    variantAuthor = lpszAuthor;
    hr = pTaskDefinition->get_RegistrationInfo(&pRegInfo);  //获取或设置用于描述任务的注册信息，例如任务的说明、任务的作者以及任务的注册日期。
    if (FAILED(hr))
    {
        qDebug() << 2;
        return FALSE;
    }
    // 设置作者信息
    pRegInfo->put_Author(variantAuthor.bstrVal);
    pRegInfo->Release();

    /* 设置登录类型和运行权限 */
    IPrincipal* pPrincipal = NULL;  //提供主体的安全凭据。 这些安全凭据定义与主体关联的任务的安全上下文。
    hr = pTaskDefinition->get_Principal(&pPrincipal);   //获取或设置为任务提供安全凭据的任务的主体。
    if (FAILED(hr))
    {
        qDebug() << 3;
        return FALSE;
    }
    // 设置登录类型
    pPrincipal->put_LogonType(TASK_LOGON_GROUP);    //获取或设置运行与主体关联的任务所需的安全登录方法。
    // 设置运行权限
    // 最高权限
    pPrincipal->put_RunLevel(TASK_RUNLEVEL_HIGHEST);    //获取或设置用于指定运行与主体关联的任务所需的特权级别的标识符。
    pPrincipal->Release();

    /* 设置其他信息 */
    ITaskSettings* pSettting = NULL;    //提供任务计划程序服务用于执行任务的设置。
    hr = pTaskDefinition->get_Settings(&pSettting);
    if (FAILED(hr))
    {
        qDebug() << 4;
        return FALSE;
    }
    // 设置其他信息
    pSettting->put_StopIfGoingOnBatteries(VARIANT_FALSE);   //设置一个布尔值，该值指示在计算机使用电池时任务将停止。
    pSettting->put_DisallowStartIfOnBatteries(VARIANT_FALSE);   //设置一个布尔值，该值指示如果计算机使用电池运行，则不会启动任务。
    pSettting->put_AllowDemandStart(VARIANT_TRUE);  //设置一个布尔值，该值指示可以使用“运行”命令或上下文菜单启动任务。
    pSettting->put_StartWhenAvailable(VARIANT_TRUE);    //设置一个布尔值，该值指示任务计划程序可以在其计划时间过后随时启动任务。
    //pSettting->put_Hidden(VARIANT_FALSE);       //设置一个布尔值，该值指示任务在 UI 中不可见。
    //这个设置系统会对任务实例判定，根据设置判断实例启动时是否需要多个实例并行，还是等待上一个实例关闭后运行
    pSettting->put_MultipleInstances(TASK_INSTANCES_STOP_EXISTING);  //指定定义任务计划程序如何处理任务的多个实例的策略。
    /*
     *  TASK_INSTANCES_POLICY 常量。     值          含义【任务设置，如果此任务已经运行，适用规则】
        TASK_INSTANCES_PARALLEL         0           在任务的现有实例正在运行时启动一个新实例。【并行运行新实例】
        TASK_INSTANCES_QUEUE            1           在任务的所有其他实例完成后，启动任务的新实例。【对新实例排队】
        TASK_INSTANCES_IGNORE_NEW       2           如果任务的现有实例正在运行，则不启动新实例。【请勿启动新实例】
        TASK_INSTANCES_STOP_EXISTING    3           在启动新实例之前停止任务的现有实例。【停止现有实例】
    */
    pSettting->Release();

    /* 创建执行动作 */
    IActionCollection* pActionCollect = NULL;       //包含任务执行的操作。
    hr = pTaskDefinition->get_Actions(&pActionCollect);
    if (FAILED(hr))
    {
        qDebug() << 5;
        return FALSE;
    }
    IAction* pAction = NULL;        //提供所有操作对象继承的公共属性。 操作对象由 IActionCollection：：Create 方法创建。
    // 创建执行操作
    pActionCollect->Create(TASK_ACTION_EXEC, &pAction); //创建一个新操作并将其添加到集合。
    pActionCollect->Release();

    /* 设置执行程序路径和参数 */
    CComVariant variantProgramPath(NULL);
    CComVariant variantParameters(NULL);
    IExecAction* pExecAction = NULL;    //Represents an action that executes a command-line operation.
    hr = pAction->QueryInterface(IID_IExecAction, (PVOID*)(&pExecAction));
    if (FAILED(hr))
    {
        pAction->Release();
        qDebug() << 6;
        return FALSE;
    }
    pAction->Release();
    // 设置程序路径和参数
    variantProgramPath = lpszProgramPath;
    variantParameters = lpszParameters;
    pExecAction->put_Path(variantProgramPath.bstrVal);  //获取或设置可执行文件的路径。
    pExecAction->put_Arguments(variantParameters.bstrVal);  //获取或设置与命令行操作关联的参数。
    pExecAction->Release();

    /* 创建触发器，实现用户登陆自启动 */
    ITriggerCollection* pTriggers = NULL;
    /*
     * ITriggerCollection：：Create
     *  [in] type
     *  此参数设置为以下 TASK_TRIGGER_TYPE2 枚举常量之一。
     *  Value	含义
     *  TASK_TRIGGER_EVENT        0
     *  发生特定事件时触发任务。
     *  TASK_TRIGGER_TIME        1
     *  在一天中的特定时间触发任务。
     *  TASK_TRIGGER_DAILY        2
     *  按每日计划触发任务。 例如，任务从每天的特定时间、每隔一天、每隔第三天等开始。
     *  TASK_TRIGGER_WEEKLY        3
     *  按每周计划触发任务。 例如，任务从每周或其他星期的特定日期的上午 8：00 开始。
     *  TASK_TRIGGER_MONTHLY        4
     *  按月计划触发任务。 例如，任务从特定月份的特定日期开始。
     *  TASK_TRIGGER_MONTHLYDOW        5
     *  按每月的星期几计划触发任务。 例如，任务在一周中的特定日期、一个月中的几周和一年中的月份开始。
     *  TASK_TRIGGER_IDLE        6
     *  当计算机进入空闲状态时触发任务。
     *  TASK_TRIGGER_REGISTRATION        7
     *  注册任务时触发任务。
     *  TASK_TRIGGER_BOOT        8
     *  在计算机启动时触发任务。
     *  TASK_TRIGGER_LOGON        9
     *  在特定用户登录时触发任务。
     *  TASK_TRIGGER_SESSION_STATE_CHANGE        11
     *  在特定会话状态更改时触发任务。
     *  [out] ppTrigger
     *  表示新触发器的 ITrigger 接口。
     *  传入对 NULLITrigger 接口指针的引用。 引用非 NULL 指针可能会导致内存泄漏，因为指针将被覆盖
    */
    hr = pTaskDefinition->get_Triggers(&pTriggers);
    if (FAILED(hr))
    {
        qDebug() << 7;
        return FALSE;
    }
    // 创建触发器
    ITrigger* pTrigger = NULL;
    hr = pTriggers->Create(TASK_TRIGGER_REGISTRATION, &pTrigger);
    if (FAILED(hr))
    {
        qDebug() << 8;
        return FALSE;
    }
    pTrigger->put_StartBoundary(_bstr_t(L"2023-09-09T08:00:00"));
    IRepetitionPattern *iRpp = NULL;
    pTrigger->get_Repetition(&iRpp);
    iRpp->put_Interval(_bstr_t(L"PT1M"));   //重复任务间隔
    // 创建触发器2
    /*
    ITrigger *pTrigger2 = NULL;
    hr = pTriggers->Create(TASK_TRIGGER_TIME, &pTrigger2);
    if (FAILED(hr))
    {
        qDebug() << "pTrigger2 ERROR";
        return FALSE;
    }
    //设置每日重复触发模式
    IDailyTrigger *pDailyTrigger = NULL;
    pTrigger2->QueryInterface(IID_IDailyTrigger,(void**)&pDailyTrigger);
    //pTrigger2->Release();
    pDailyTrigger->put_StartBoundary(_bstr_t(L"2023-09-09T08:00:00"));

    IRepetitionPattern *iRpp = NULL;
    pDailyTrigger->get_Repetition(&iRpp);
    //触发器高级设置
    //iRpp->put_Duration(_bstr_t(L"PT5M"));   //持续时间
    iRpp->put_Interval(_bstr_t(L"PT1M"));   //重复任务间隔
    */

    /* 注册任务计划  */
    IRegisteredTask* pRegisteredTask = NULL;    //提供用于立即运行任务、获取任务的任何正在运行的实例、获取或设置用于注册任务的凭据以及描述任务的属性的方法。
    CComVariant variantTaskName(NULL);
    variantTaskName = lpszTaskName;
    hr = m_lpRootFolder->RegisterTaskDefinition(variantTaskName.bstrVal,    //任务的名称
                                                pTaskDefinition,    //已注册任务的定义
                                                TASK_CREATE_OR_UPDATE,  //TASK_CREATION常量
                                                _variant_t(),   //L"SYSTEM" 用于注册任务的用户凭据(留空默认是启动程序的用户注册登录后运行) 如果存在，这些凭据优先于 pDefinition 参数指向的任务定义对象中指定的凭据。
                                                _variant_t(),   //用于注册任务的 userId 的密码。 使用TASK_LOGON_SERVICE_ACCOUNT登录类型时，密码必须是空 VARIANT 值，例如 VT_NULL 或 VT_EMPTY。
                                                TASK_LOGON_INTERACTIVE_TOKEN,   //定义用于运行已注册任务的登录技术。
                                                _variant_t(""),     //与已注册的任务关联的安全描述符。 可以在任务的安全描述符中指定访问控制列表 (ACL) ，以允许或拒绝某些用户和组访问任务。
                                                &pRegisteredTask);  //表示新任务的 IRegisteredTask 接口。
    if (FAILED(hr))
    {
        pTaskDefinition->Release();
        qDebug() << QString("%1").arg(hr,16);
        MessageBoxW((HWND)this->winId(),L"未使用管理员权限运行",L"Error",MB_OK);
        return FALSE;
    }
    pTaskDefinition->Release();
    pRegisteredTask->Release();
    return TRUE;
}

//卸载COM组件
void Widget::UnInit() {
    if (m_lpITS)
    {
        m_lpITS->Release();
    }
    if (m_lpRootFolder)
    {
        m_lpRootFolder->Release();
    }
    ::CoUninitialize();
}


BOOL Widget::DeleteTask(char *lpszTaskName)
{
    if (NULL == m_lpRootFolder)
    {
        return FALSE;
    }
    CComVariant variantTaskName(NULL);
    variantTaskName = lpszTaskName;
    HRESULT hr = m_lpRootFolder->DeleteTask(variantTaskName.bstrVal, 0);
    if (FAILED(hr))
    {
        return FALSE;
    }

    return TRUE;
}
